package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"github.com/gin-contrib/static"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	ginSwag "github.com/swaggo/gin-swagger"
	"github.com/swaggo/gin-swagger/swaggerFiles"
	"net/http"
	"os"
	"os/signal"
	"smart-sensor-cloud/internal/config"
	_ "smart-sensor-cloud/internal/docs"
	"smart-sensor-cloud/internal/model"
	"smart-sensor-cloud/internal/router"
	"smart-sensor-cloud/internal/service"
	"smart-sensor-cloud/internal/version"
	"smart-sensor-cloud/pkg/auth"
	"smart-sensor-cloud/pkg/auth/store/redis"
	"smart-sensor-cloud/pkg/hook"
	"smart-sensor-cloud/pkg/log"
	M "smart-sensor-cloud/pkg/middleware"
	"syscall"
	"time"
)

var (
	middleware = []gin.HandlerFunc{
		gin.Recovery(),
		M.CORSMiddleware(),
		M.RequestID(),
		M.APIClientMiddleware,
		M.DeviceFSMMiddleware(),
		M.BacktraceM(),
		M.FaultHandler,
	}

	routers = []*router.RouteGroup{
		{
			BasePath:   "/",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodPost, "/captcha", service.Captcha},
				{http.MethodPost, "/login", service.Login},
				{http.MethodGet, "/info", service.GetUserInfo},
				{http.MethodGet, "/logout", service.Logout},
				{http.MethodGet, "/refresh-token", service.RefreshToken},
				{http.MethodGet, "/menu.tree", service.GetCurrentUserMenuTree},
				{http.MethodPost, "/syslog", service.Syslog},
			},
		},
		{
			BasePath:   "/select",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodGet, "/roles", service.GetRoleSelections},
			},
		},
		{
			BasePath:   "/tree",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodPost, "/menus", service.GetMenuTree},
			},
		},
		{
			BasePath:   "/user",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodPost, "", service.AddUser},
				{http.MethodPut, "/:id", service.UpdateUser},
				{http.MethodDelete, "/:id", service.DeleteUser},
				{http.MethodGet, "", service.GetUsers},
				{http.MethodGet, "/:id", service.GetUserWithID},
				{http.MethodPost, "/role", service.UpdateUserRole},
				{http.MethodPost, "/chgpwd", service.ChangePwdWithUsername},
			},
		},
		{
			BasePath:   "/role",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodGet, "", service.GetRoles},
				{http.MethodGet, "/:id", service.GetRoleWithID},
				{http.MethodPost, "", service.AddRole},
				{http.MethodPut, "/:id", service.UpdateRole},
				{http.MethodDelete, "/:id", service.DeleteRole},
				{http.MethodPost, "/menu/:id", service.UpdateRoleMenuWithRoleID},
				//{http.MethodGet, "/child/:id", service.GetRolesWithParentID},
			},
		},
		{
			BasePath:   "/menu",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodGet, "", service.GetMenus},
				{http.MethodGet, "/:id", service.GetMenuWithID},
				{http.MethodPost, "", service.AddMenu},
				{http.MethodPut, "/:id", service.UpdateMenu},
				{http.MethodDelete, "/:id", service.DeleteMenu},
			},
		},
		{
			BasePath:   "/log",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodPost, "", service.GetLogs},
				{http.MethodDelete, "/:id", service.DeleteLog},
				{http.MethodPost, "/batch", service.DeleteLogs},
			},
		},
		{
			BasePath:   "/project",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodGet, "", service.GetProjects},
				{http.MethodGet, "/:id", service.GetProjectWithID},
				{http.MethodPost, "", service.AddProject},
				{http.MethodPut, "/:id", service.UpdateProject},
				{http.MethodDelete, "/:id", service.DeleteProject},
			},
		},
		{
			BasePath:   "/section",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodPost, "", service.GetSections},
			},
		},
		{
			BasePath:   "/unit",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodPost, "", service.GetUnits},
			},
		},
		{
			BasePath:   "/jwt",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodPost, "/jsonInBlacklist", service.JsonInBlacklist},
			},
		},
		{
			BasePath:   "/device",
			Middleware: &middleware,
			Routes: []*router.Route{
				// FIXME: 正常情况这里是不应该出现Router Conflict的, Gin的前缀树认为此Router和Device的wildcard参数(/device/:id)冲突了
				{http.MethodPost, "/type", service.GetDeviceTypes},
				{http.MethodGet, "", service.GetDevices},
				{http.MethodGet, "/:id", service.GetDeviceWithID},
				{http.MethodPost, "", service.AddDevice},
				{http.MethodPut, "/:id", service.UpdateDevice},
				{http.MethodDelete, "/:id", service.DeleteDevice},
				{http.MethodPatch, "/:id/confirm/:type", service.DeviceConfirm},
				{http.MethodPatch, "/:id/delete", service.DeleteDevice},
			},
		},
		{
			BasePath:   "/ledger",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodGet, "", service.GetLedgers},
				{http.MethodGet, "/:id", service.GetLedgerByID},
				{http.MethodPost, "", service.AddLedger},
				{http.MethodPut, "/:id", service.UpdateLedger},
				{http.MethodDelete, "/:id", service.DeleteLedger},
			},
		},
		{
			BasePath:   "/app",
			Middleware: &middleware,
			Routes: []*router.Route{
				{http.MethodGet, "/health", func(context *gin.Context) {
					context.JSON(http.StatusOK, gin.H{
						"code":   200,
						"status": "HEALTHY",
					})
				}},
			},
		},
		{
			BasePath:   "/host",
			Middleware: &middleware,
			Routes: []*router.Route{
				// FIXME: 正常情况这里是不应该出现Router Conflict的, Gin的前缀树认为此Router和Device的wildcard参数(/device/:id)冲突了
				//{http.MethodPost, "/type", service.GetDeviceTypes},
				{http.MethodGet, "", service.GetHosts},
				{http.MethodGet, "/:id", service.GetHostWithID},
				{http.MethodPost, "", service.AddHost},
				{http.MethodPut, "/:id", service.UpdateHost},
				{http.MethodDelete, "/:id", service.DeleteHost},
			},
		},
	}

	configPath = flag.String("conf", "./config.json", "specified config file path used for application")
)

// @title Smart Sensor Cloud API
// @version 1.0
// @description Smart Sensor Cloud API based on RESTful API design specification
// @contact.name Gavin.S
// @contact.url https://github.com/sunzhenyucn
// @contact.email sunzhenyucn@gmail.com
// @BasePath /

func main() {
	// // setting GIN working on Release mode
	// gin.SetMode(gin.ReleaseMode)

	flag.Parse()

	// init config
	conf, err := config.LoadFromFile(*configPath)
	if err != nil {
		panic(err)
	}

	var redisManager *redis.Manager
	// init JWT generator
	if conf.Redis.Enabled {
		redisManager = redis.NewManager(&conf.Redis)
		if err := redisManager.Ping(); err != nil {
			panic(err)
		}
	}

	jwt := &auth.JWTGenerator{
		SigningKey:   []byte(conf.Server.Secret),
		RedisManager: redisManager,
		RedisEnabled: conf.Redis.Enabled,
	}

	// setting logger
	log.SetFormat(conf.Logger.Format)
	log.SetLevel(conf.Logger.Level)
	log.SetApplicationName(conf.Logger.ApplicationName)
	log.SetVersion(version.Version)
	log.SetBuild(version.Build)

	// setting logger hook
	if err := hook.InitHook(conf); err != nil {
		panic(err)
	}

	// JWT generator middleware
	middleware = append(middleware,
		M.JWTGeneratorMiddleware(jwt),
		M.JWTAuthMiddleware(M.AllowedPathPrefixSkipper("/login", "/captcha", "/syslog")),
		M.LoggerM(conf.Server.MaxLoggedContentLength),
	)

	// init handle in middleware
	M.InitAPIClient(conf.APIHost, conf.APIToken)

	// init fsm in middleware
	M.InitDeviceFSM()

	// init database connection
	if err := model.ConnectDatabaseWithConfig(&conf.MySQL); err != nil {
		panic(err)
	}

	// init menu data
	model.InitMenu(conf.Application.Initialize.MenuDataPath)

	// init application administrator account
	model.InitRootUser(&conf.Application.Root)

	// init casbin
	if enforcer, err := auth.InitCasbin(&conf.Casbin); err != nil {
		panic(err)
	} else {
		middleware = append(middleware, M.CasbinMiddleware(
			conf.Casbin.Enabled,
			enforcer,
			M.AllowedPathPrefixSkipper("/login", "/logout", "/captcha", "/menu.tree", "/user/chgpwd", "/syslog"),
		))
	}

	// load routers
	r := router.LoadAndCreateRouter(routers)

	// if swagger enabled is true, registration the swagger handler into router
	if conf.SwaggerEnabled {
		// integration with Swagger (using swag)
		r.GET("/swagger/*any", ginSwag.WrapHandler(swaggerFiles.Handler))
	}

	r.StaticFS("/web", static.LocalFile("./dist", false))

	s := &http.Server{
		Addr:              fmt.Sprintf(":%d", conf.Server.Port),
		Handler:           r,
		ReadTimeout:       time.Duration(conf.Server.ReadTimeout) * time.Second,
		ReadHeaderTimeout: time.Duration(conf.Server.ReadHeaderTimeout) * time.Second,
		WriteTimeout:      time.Duration(conf.Server.WriteTimeout) * time.Second,
		IdleTimeout:       time.Duration(conf.Server.IdleTimeout) * time.Second,
		MaxHeaderBytes:    conf.Server.MaxHeaderBytes,
	}

	// run background routine for application's http server
	go func() {
		if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			logrus.Fatalf("%s listen and server at %d failed: %s", conf.Logger.ApplicationName, conf.Server.Port, err)
		}
	}()

	//sdwan_data.InitHttpClient()
	//
	//c := cron.New()
	//c.AddFunc("@every 300s", func() { sdwan_data.Login2GetCookie() })
	//c.AddFunc("@every 5s", func() { sdwan_data.GetSystemOptions() })
	////c.AddFunc("@every 3s", func() { sdwan_data.GetEthernetStats() })
	////c.AddFunc("@every 3s", func() { sdwan_data.GetWanLinkUsageStats() })
	//c.Start()

	logrus.Infof("%s listen and serve at %d", conf.Logger.ApplicationName, conf.Server.Port)

	// graceful shutdown for application's http server
	signalC := make(chan os.Signal, 1)
	signal.Notify(signalC, syscall.SIGINT, syscall.SIGTERM)
	// hang routine till os syscall signal has catch into signalC
	<-signalC

	logrus.Infof("%s is shutting down...", conf.Logger.ApplicationName)

	// shutting application's http server with context
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()
	if err := s.Shutdown(ctx); err != nil {
		logrus.Fatalf("shutdown %s has error, need to force shutting down: %s", conf.Logger.ApplicationName, err)
	}
	logrus.Infof("%s has exited.", conf.Logger.ApplicationName)
}
